﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using Google.Api.Maps.Service.StaticMaps;

namespace NextViewer
{
   
    public class Spots
    {
        internal List<Spot> atom;
        public Spots()
        {
            atom = new List<Spot>();
        }
    }
    /// <summary>
    /// path는 V-spot들의 모임이다. 또한 각 Path는 고유의 ID를 가진다. Path의 ID는 Path가 읽혀질때 생성되며 Global한 값은 아니다.
    /// path는 출발과 도착시간(approximate) 을 가지며 출발 Location과 도착 Location을 가진다.
    /// 또한 출발한 Loc전의 출발Loc가 있다면 역시 가지고 있다. 따라서 DFS로 서치하면 긴 패스를 역으로 읽어 올 수 있다.
    /// </summary>
    public class Path 
    {
        public Path()
        {
            con = new Spots();
        }
        public int ID;
        internal DateTime sTime;
        internal DateTime eTime;
        internal int sLoc;
        internal int eLoc;
        internal int preLoc;

        internal Spots con;
    }
    /// <summary>
    /// Location은 특정 시간 이상 머무른 것으로 추정되는 지점이다. 주변에 충분히 많은 점들이 해당 위치를 중심으로 분포되어 있다
    /// 이 점들 덕분에 Location이 특정되어 질 수 있고 이 점들의 분포를 이용하여 Clustering을 할 수 있다.
    /// 
    /// </summary>
    public class CLocation
    {
        public CLocation()
        {
            con = new Spots();
        }
        public CLocation(Spot sp)
        {
            con = new Spots();
            con.atom.Add(sp);
            app_lat = sp.lat;
            app_lon = sp.lon;
            app_dist = Glovar.networkGap;
        }
        internal double dayofTime=0;  //하루평균 머무른 분입니다.
        internal double weekofTime=0; //한주 평균 머무른 분입니다.

        internal Spots con;

        internal double app_lat=0;    //추정되는 정확한 위치와 확률
        internal double app_lon=0;
        internal double app_dist=0;
        internal double app_err=0.9;
        public int id;

        public static int nID = 1;
        public static int GetNextID()
        {
            return nID++;
        }
        /// <summary>
        /// 확률 모델 또는 몬테카를로를 이용하여 가상좌표를 얻어낸다.
        /// </summary>
        public void Approximation()
        {
            if (con.atom.Count == 0)
                return;
            //일단 간단히 다 더해서 센터를 구한다.
            double lat=0, lon=0;

            for (int i = 0; i < con.atom.Count; i++)
            {
                lat += con.atom[i].lat;
                lon += con.atom[i].lon;
            }
            app_lat = lat / con.atom.Count;
            app_lon = lon / con.atom.Count;

            // monte carlo 같이 길이를 서서히 줄여나가며 approximation 했을때 
            // 전체 spot의 app_err % 이상이 나오는 가장 짧은 거리를 찾는다.
            double dist = Glovar.networkGap; //(km)
            
            
            //10m 단위로 깎는다.
            double gap = 0.01;
            int count;
            Spot me = new Spot();
            me.lat = app_lat;
            me.lon = app_lon;


            // down phase
            for (int i = 0; i < (int)(Glovar.networkGap * 1000) / (int)(gap*1000); i++)
            {
                dist -= gap;
                count =0 ;
                for (int j = 0; j < con.atom.Count; j++)
                {
                    if(isSameLoc(me, con.atom[j], dist))
                        count++;
                }
                if (count < con.atom.Count * app_err || dist <= gap)   //1m이하까지 떨어지면 그냥 10m로 한다
                    break;
            }

            // 1m 씩 올린다.
            gap = 0.001;
            // up phase
            for (int i = 0; true; i++)
            {
                dist += gap;
                count = 0;
                for (int j = 0; j < con.atom.Count; j++)
                {
                    if (isSameLoc(me, con.atom[j], dist))
                        count++;
                }
                if (count >= con.atom.Count * app_err)
                    break;
            }
            app_dist = dist;
            //여기에 도착하면 언제나 확률 app_err만큼의 app_lat,lon, dist가 생긴다.

        }
        public void AddSpot(Spot sp)
        {
            this.con.atom.Add(sp);
            Approximation();
        }
        public bool IsIn(Spot sp)
        {
            Spot me = new Spot();
            me.lat = app_lat;
            me.lon = app_lon;

            return isSameLoc(me, sp, Glovar.networkGap);
        }

        public static bool isSameLoc(Spot s1, Spot s2, double minDist)
        {
            double dist = Util.GetDistance(s1, s2);
            if (dist <= minDist )
            {
                return true;
            }
            return false;
        }
    }
    
    
    /// Edge
    /// edge는 방향을 가지며 가중치가 없다. 그리고 edge는 여러개의 V-spot으로 구현되며
    /// 이들은 클러스터링에서 발생되는 L1 -> L2 의 path를 모두 모아서 가지게 된다.
    /// 즉 하나의 edge는 단지 목적지와 출발지로 규정지어 지며 교통수단, 루트 등에 따라 다르게 편성
    /// 된 여러개의 path들이 내제 되어 있다 이러한 path들은 모두 자신의 고유 정보를 그대로 가지게 된다.
    /// 또한 특이하게 Edge는 여러 Path의 각 previous path를 규정할 수 있게 하였다 이로서
    /// 가장 긴 하나의 path를 규정할 수 있게 하였다. 
    public class CEdge
    {
        public CEdge()
        {
            con = new List<Path>();
        }
        internal int sLoc;
        internal int eLoc;
        internal List<Path> con;
    }
    /// 
    /// 
    ///Node
    ///노드는 클러스터링에서 찾아진 볼륨 클러스터 L의 대표점을 의미하며 L은 여러개의
    ///L-spot으로 이루어진다. 이 L-spot들을 이용하여 L의 대표점을 구하게 된다.
    ///

    ///
    /// cluster의 forest
    /// node는 location을 edge는 path 를 의미한다. 
    /// 
    public class CForest
    {
        public CForest()
        {
            Locs = new Hashtable();
            Edges = new List<Hashtable>();
            CLocation.nID = 1;
            
        }
        
        public Hashtable Locs; //<LocationID, CLocation>
        public List<Hashtable> Edges;   //LIST<LocationID>, HashTable <LocationID, CEdge>

        public void AddPath(Path path)
        {
            
        }
        public int GetLocation(Spot sp)
        {
            foreach (DictionaryEntry de in Locs)
            {
                if (((CLocation)de.Value).IsIn(sp))
                {
                    return (int)de.Key;
                }
            }
            return -1;
        }
        public void AddLocation(CLocation loc)
        {
            Locs[loc.id] = loc;
            Edges.Insert(loc.id,new Hashtable());
        }
        
        public void AddLSpot(int locID, Spot sp)
        {
            CLocation loc;
            loc = (CLocation)Locs[locID];
            loc.AddSpot(sp);

            Locs.Remove(locID);
            Locs[locID] = loc;

        }
        public void AddEdge(Path path)
        {
            CEdge edge;
            if (Edges[path.sLoc].Contains(path.eLoc))
            {
                edge = (CEdge)Edges[path.sLoc][path.eLoc];
                Edges[path.sLoc].Remove(path.eLoc);
            }
            else
            {
                edge = new CEdge();
                edge.sLoc = path.sLoc;
                edge.eLoc = path.eLoc;
            }
            edge.con.Add(path);
            Edges[path.sLoc][path.eLoc] = edge;
        }

        public void Show(NextViewer nv)
        {
            StaticMap map = new StaticMap();
            map.Size = "640x640";
            map.InitPath();

            int i = 0;
            for (i = 1; i < this.Locs.Count; i++)
            {
                CLocation cl = (CLocation)Locs[i];
                map.AddSeqMarker(cl.app_lat, cl.app_lon);

                nv.SetText("LOC[" +(i - 1) + "] (" + cl.app_lat + ", " + cl.app_lon + ") dist:" + cl.app_dist);
            }
            for (i = 1; i < this.Edges.Count;i++ )
            {
                foreach (DictionaryEntry de in Edges[i])
                {
                    nv.SetText("Path [" + (i-1) + "] to [" + ((int)de.Key-1) + "] count : " + ((CEdge)de.Value).con.Count);
                }


            }

            nv.updatePic(map);
            nv.SetText(map.ToUri().ToString());
            
        }
        public void Stat(NextViewer nv)
        {
            Stats<int> stat = new Stats<int>();
            CLocation cl;
            int dist;
            
            foreach (DictionaryEntry de in Locs)
            {
                cl = (CLocation)de.Value;
                
                foreach (Spot sp in cl.con.atom)
                {
                    dist =(int)(1000* Util.GetDistance(sp.lat, sp.lon, cl.app_lat, cl.app_lon));
                    
                    nv.SetText("" + (dist));
                    stat.Add(dist);
                }
            }
            nv.SetText("AVR : " + stat.GetAvr() + " STDEV : " + stat.GetStdev());
        }
    }
    /// <summary>
    /// 클러스터링은 3가지 방법을 사용합니다.
    /// 일단 GA를 제외한 K-means와 Threathold방식만을 추가함
    /// </summary>
    class Clustering
    {
        public Clustering(Commands cm)
        {
            this.cmd = cm;
        }
        public void Threathold()
        {
            Spots raw = new Spots();
            Spot.GetSpots(raw.atom,cmd.user, cmd.dtStart, cmd.dtEnd);

            CForest forest = new CForest();
            



            int i;
            int skiped = 0;

            Spot spot, prespot = null;
            LSpot prelspot = null;
            VSpot prevspot = null;
            
            Path path = new Path();

            if (raw.atom.Count < 2)
                return;

            //가상의 시작점 0번을 등록한다.
            CLocation loc = new CLocation();
            loc.id = 0;
            forest.AddLocation(loc);
            


            prespot = raw.atom[0];
            for (i = 1; i < raw.atom.Count; i++)
            {
                spot = raw.atom[i];
                if ((spot.gps == -1 || spot.gps == 2))
                // && (spot.wifi == -1 || spot.wifi != 2))   //off
                {
                    if (spot.lat == -1 || spot.lat == 0 || spot.lon == 0 || spot.lon == -1)
                        continue;
                    double dist = Util.GetDistance(spot, prespot);
                    double speed = dist / (spot.time - raw.atom[i - 1].time).TotalSeconds;
                    if (dist == 0 || speed > Glovar.maxSpeed)
                        continue;

                    if (Glovar.networkGap > dist)
                    {

                        if (prespot.type == Spot.LSPOT || prespot.type == Spot.SPOT)
                        {
                            if (prelspot == null)
                            {
                                prelspot = new LSpot(prespot);
                            }
                            prelspot.tout = spot.time;
                            prelspot.overlapped++;
                            prespot = prelspot;
                            int locID = forest.GetLocation(spot);
                            if (locID == -1)
                            {
                                locID = CLocation.GetNextID();
                                CLocation lc = new CLocation(spot);
                                lc.id = locID;
                                forest.AddLocation(lc);
                            }
                            else
                                forest.AddLSpot(locID, spot);

                            continue;
                        }
                        else
                        {
                            //direction analysis
                            //in network we wait 1h ...
                            if ((spot.time - prespot.time).TotalHours > Glovar.networkHour)
                            {
                                //here is location
                                prelspot = new LSpot(prespot);
                                prelspot.tout = spot.time;
                                prelspot.overlapped = skiped + 1;
                                skiped = 0;
                                prespot = prelspot;
                                //path.Add(prespot);
                                
                                //일단 spot은 location으로 보내고
                                int locID = forest.GetLocation(spot);
                                if (locID == -1)
                                {
                                    locID = CLocation.GetNextID();
                                    CLocation lc = new CLocation(spot);
                                    lc.id = locID;
                                    forest.AddLocation(lc);
                                }
                                else
                                    forest.AddLSpot(locID, spot);

                                //기존 VSpot이 가지는 path는 위의 location을 끝점을 삼는다.
                                path.eLoc = locID;
                                path.eTime = prespot.time;
                                forest.AddEdge(path);
                                Path path2 = path;
                                path = new Path();
                                path.preLoc = path2.sLoc;
                                path.sLoc = locID;
                            }
                            else
                            {
                                skiped++;
                                //skip
                            }
                        }
                    }
                    else // vspot
                    {
                        prevspot = new VSpot(spot);
                        GetDirection(prespot, prevspot);
                        //if ((spot.time - prespot.time).TotalHours > 1)
                        //{
                        //    prespot = new LSpot(prespot);
                        //}
                        //if(prespot.type != Spot.LSPOT)
                        path.con.atom.Add(prespot);
                        //if (prespot.type == Spot.LSPOT)
                        //    i = i;
                        prespot = prevspot;
                    }
                }
                else//GPS & WIFI
                {

                }

            }
            //마지막 노드를 저장합니다.
            //if (path.Count == 0)
            //    path.Add(prespot);
            //else if (path[path.Count - 1] != prespot)
            //    path.Add(prespot);


            forest.Show(this.cmd.mnv);
            forest.Stat(this.cmd.mnv);
        }
        public void GetDirection(Spot prev, VSpot cur)
        {
            double dx, dy;
            dx = cur.lat - prev.lat;
            dy = cur.lon - prev.lon;

            double len = Math.Sqrt(dx * dx + dy * dy);

            cur.dx = dx / len;
            cur.dy = dy / len;

        }

        Commands cmd;
        
    }
}
